﻿namespace DataDuctus.MongoDb.Serialization.Extensions
{
	using System;
	using System.Collections.Generic;
	using System.Linq;
	using System.Reflection;

	using DataDuctus.MongoDb.Serialization.Utils;

	using MongoDB.Bson.Serialization;

	internal static class ReflectionExtensions
	{
		internal static Type ExtractType(this MemberInfo member)
		{
			return (member is PropertyInfo) ? ((PropertyInfo)member).PropertyType : ((FieldInfo)member).FieldType;
		}

		internal static bool HasBaseTypeAttribute<T>(this MemberInfo member) where T : Attribute
		{
			if (member is PropertyInfo)
			{
				var pinfo = (PropertyInfo)member;

				var methdInfo = pinfo.GetGetMethod().GetBaseDefinition();

				if (methdInfo.DeclaringType == member.DeclaringType)
				{
					return false;
				}

				var prop = methdInfo.DeclaringType.GetProperty(
					member.Name, BindingFlags.Instance | BindingFlags.DeclaredOnly | BindingFlags.Public);

				return prop.HasAttribute<T>();
			}

			return false; // Fields cannot override - just conflict
		}

		internal static IEnumerable<ShouldSerializeMethodAndName> ToShouldSerializeMethods(this Type type)
		{
			return (from meth in type.GetMethods(BindingFlags.Instance | BindingFlags.Public)
							where
								meth.Name.StartsWith("ShouldSerialize") &&
								meth.ReturnType.Equals(typeof(void)) &&
								0 == meth.GetParameters().Length
							select new ShouldSerializeMethodAndName
								{
									Method = meth,
									PropertyName = meth.Name.Substring(15)
								}).ToArray();
		}

		#region Register Type
		internal static IEnumerable<Type> RegisterType(this Type type)
		{
			if (BsonClassMap.IsClassMapRegistered(type))
			{
				return StaticDefaults.EmptyTypeEnumerable;
			}

			// Create regclass.
			var reg = typeof(ReflectionExtensions)
				.GetMethod("Create", BindingFlags.Static | BindingFlags.NonPublic)
				.MakeGenericMethod(type)
				.Invoke(null, null);

			// Register
			return (IEnumerable<Type>)reg.GetType()
				.GetMethod("Register", BindingFlags.Public | BindingFlags.Instance)
				.Invoke(reg, null);
		}

		// ReSharper disable UnusedMember.Local
		private static DataContractMapper<T> Create<T>()
		{
			return new DataContractMapper<T>();
		}
		// ReSharper restore UnusedMember.Local
		#endregion // Register Type

		#region ShouldSerializeMethodAndName
		internal class ShouldSerializeMethodAndName
		{
			internal MethodInfo Method;

			internal string PropertyName;
		}
		#endregion // ShouldSerializeMethodAndName
	}
}